#include <cstdio>
#include <algorithm>
//最小生成树简单来说就是从一个n个节点m个边的连通图里找出n-1条边组成一颗树
//同时使得这个树的权值和最小
using namespace std;
const int MAXN=10050;
//节点数和边数
int n,m;
//u[i] v[i] w[i]分别表示第i条边的左端点，右端点和权值
//p[i]是辅助的并查集
//v[i]是表示第i条边的长度排名(通过间接排序)
int u[MAXN],v[MAXN],w[MAXN],p[MAXN],r[MAXN];
//间接排序函数！比较的是w[i]，但排序(交换)的是i，也就是排序的是调用sort方法的数组
int cmp(const int i,const int j){
	return w[i]<w[j];
}
//并查集根查找，带路径压缩
int find(int x){
	return p[x]==x ? x : p[x]=find(p[x]);
}
//Kruskal算法求最小生成树
int Kruskal(){
	//最终返回生成树的权值和
	int ans=0;
	//初始化并查集，每个点独立一个连通分量
	for(int i=0;i<n;i++){
		p[i]=i;
	}
	//初始化r数组加排序，此时r数组中保存的是对应边的排行
	for(int i=0;i<m;i++){
		r[i]=i;
	}
	sort(r,r+m,cmp);
	for(int i=0;i<m;i++){
		//可以看成是一个优先队列，r[i]保存的就是权值第i小的边的编号
		int e=r[i];
		//printf("%d\n",e);
		//看是否连通
		int x=find(u[e]);
		int y=find(v[e]);
		//如果不连通，就将这条边加入最小生成树，并连通这两条边
		if(x!=y){
			ans+=w[e];
			p[x]=y;
		}
	}
	return ans;
}
int main(void){
	scanf("%d%d",&n,&m);
	int ui,vi,wi;
	for(int i=0;i<m;i++){
		scanf("%d%d%d",&ui,&vi,&wi);
		u[i]=ui;
		v[i]=vi;
		w[i]=wi;
	}
	printf("%d\n",Kruskal());
	return 0;
}
